<html><head><title>Everyone Out of the Pool! Tapestry Goes Singleton!</title></head><body><div id='tit'>Everyone Out of the Pool! Tapestry Goes Singleton!</div><div id='cate'>源码品读</div><div id='date'>2010年08月31日 星期二 12:02 P.M.</div><div id='page'>9</div><a id='url' href='http://hi.baidu.com/hxzon/blog/item/2cbb96efd5ed86e1ce1b3e80.html'>http://hi.baidu.com/hxzon/blog/item/2cbb96efd5ed86e1ce1b3e80.html</a><div id='cnt'><h1>Everyone Out of the Pool! Tapestry Goes Singleton!</h1> 
<p>http://java.dzone.com/articles/everyone-out-pool-tapestry?utm_source=feedburner&amp;utm_medium=feed&amp;utm_campaign=Feed%3A+javalobby%2Ffrontpage+%28Javalobby+%2F+Java+Zone%29</p> 
<p>Tapestry applications are inherently <em>stateful</em>: during and between requests, information in Tapestry components, value stored in fields, stick around. This is a great thing: it lets you program a web application in a sensible way, using stateful objects full of mutable properties and methods to operate on those properties.</p> 
<p>It also has its downside: Tapestry has to maintain a <em>pool</em> of page instances. And in Tapestry, page instances are <em>big</em>: a tree of hundreds or perhaps thousands of interrelated objects: the tree of Tapestry structural objects that forms the basic page structure, the component and mixin objects hanging off that tree, the binding objects that connect parameters of components to properties of their containing component, the template objects that represents elements and content from component templates, and many, many more that most Tapestry developers are kept unawares of.</p> 
<p>This has proven to be a problem with biggest and busiest sites constructed using Tapestry. Keeping a pool of those objects, checking them in and out, and discarded them when no longer needed is draining needed resources, especially heap space.</p> 
<p>So that seems like an irreconcilable problem eh? Removing mutable state from pages and components would turn Tapestry into something else entirely. On the other hand, allowing mutable state means that applications, especially big complex applications with many pages, become memory hogs.</p> 
<p>I suppose one approach would be to simply create a page instance for the duration of a request, and discard it at the end. However, page construction in Tapestry is very complicated and although some effort was expended in Tapestry 5.1 to reduce the cost of page construction, it is still present. Additionally, Tapestry is full of small optimizations that improve performance ... assuming a page is reused over time. Throwing away pages is a non-starter.</p> 
<p>So we're back to square one ... we can't eliminate mutable state, but (for large applications) we can't live with it either.</p> 
<p>Tapestry has already been down this route: the way persistent fields are handled gives the <em>illusion</em> that the page is kept around between requests. You might think that Tapestry serializes the page and stores the whole thing in the session. In reality, Tapestry is shuffling just the individual persistent field values in to and out of the HttpSessio. To both the end user and the Tapestry developer, it feels like the entire page <em>is</em> live between requests, but it's a bit of a shell game, providing an equivalent page instance that has the same values in its fields.</p> 
<p>What's going on in trunk right now is extrapolating that concept from just persistent fields to <em>all</em> mutable fields. Every access to every mutable field in a Tapestry page is converted, as part of the class transformation process, into an access against a per-thread Map of keys and values. Each field gets a unique identifying key. The Map is discarded at the end of the request.</p> 
<p>The end result is that a <em>single</em> page instance can be used <em>across multiple threads</em> without any synchronization issues and without any field value conflicts.</p> 
<p>This idea was suggested in years past, but the APIs to accomplish it (as well as the necessary meta-programming savvy) just wasn't available. However, as a side effect of rewriting and simplifying the class transformation APIs in 5.2, it became very reasonable to do this.</p> 
<p>Let's take an important example: the handling of typical, mutable fields. This is the responsibility of the UnclaimedFieldWorker class, part of Tapestry component class transformation pipeline. UnclaimedFieldWorker finds fields that have not be &quot;claimed&quot; by some other part of the pipeline and converts them to read and write their values to the per-thread Map. A claimed field may store an injected service, asset or component, or be a component parameter.</p> 
<pre>public class UnclaimedFieldWorker implements ComponentClassTransformWorker<br />{<br />    private final PerthreadManager perThreadManager;<br /><br />    private final ComponentClassCache classCache;<br /><br />    static class UnclaimedFieldConduit implements FieldValueConduit<br />    {<br />        private final InternalComponentResources resources;<br /><br />        private final PerThreadValue&lt;Object&gt; fieldValue;<br /><br />        // Set prior to the containingPageDidLoad lifecycle event<br />        private Object fieldDefaultValue;<br /><br />        private UnclaimedFieldConduit(InternalComponentResources resources, PerThreadValue&lt;Object&gt; fieldValue,<br />                Object fieldDefaultValue)<br />        {<br />            this.resources = resources;<br /><br />            this.fieldValue = fieldValue;<br />            this.fieldDefaultValue = fieldDefaultValue;<br />        }<br /><br />        public Object get()<br />        {<br />            return fieldValue.exists() ? fieldValue.get() : fieldDefaultValue;<br />        }<br /><br />        public void set(Object newValue)<br />        {<br />            fieldValue.set(newValue);<br /><br />            // This catches the case where the instance initializer method sets a value for the field.<br />            // That value is captured and used when no specific value has been stored.<br /><br />            if (!resources.isLoaded())<br />                fieldDefaultValue = newValue;<br />        }<br /><br />    }<br /><br />    public UnclaimedFieldWorker(ComponentClassCache classCache, PerthreadManager perThreadManager)<br />    {<br />        this.classCache = classCache;<br />        this.perThreadManager = perThreadManager;<br />    }<br /><br />    public void transform(ClassTransformation transformation, MutableComponentModel model)<br />    {<br />        for (TransformField field : transformation.matchUnclaimedFields())<br />        {<br />            transformField(field);<br />        }<br />    }<br /><br />    private void transformField(TransformField field)<br />    {<br />        int modifiers = field.getModifiers();<br /><br />        if (Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers))<br />            return;<br /><br />        ComponentValueProvider&lt;FieldValueConduit&gt; provider = createFieldValueConduitProvider(field);<br /><br />        field.replaceAccess(provider);<br />    }<br /><br />    private ComponentValueProvider&lt;FieldValueConduit&gt; createFieldValueConduitProvider(TransformField field)<br />    {<br />        final String fieldName = field.getName();<br />        final String fieldType = field.getType();<br /><br />        return new ComponentValueProvider&lt;FieldValueConduit&gt;()<br />        {<br />            public FieldValueConduit get(ComponentResources resources)<br />            {<br />                Object fieldDefaultValue = classCache.defaultValueForType(fieldType);<br /><br />                String key = String.format(&quot;UnclaimedFieldWorker:%s/%s&quot;, resources.getCompleteId(), fieldName);<br /><br />                return new UnclaimedFieldConduit((InternalComponentResources) resources,<br />                        perThreadManager.createValue(key), fieldDefaultValue);<br />            }<br />        };<br />    }<br />}</pre> 
<p>That seems like a lot, but lets break it down bit by bit.</p> 
<pre>public void transform(ClassTransformation transformation, MutableComponentModel model)<br />{<br />    for (TransformField field : transformation.matchUnclaimedFields())<br />    {<br />        transformField(field);<br />    }<br />}<br /><br />private void transformField(TransformField field)<br />{<br />    int modifiers = field.getModifiers();<br /><br />    if (Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers))<br />        return;<br /><br />    ComponentValueProvider&lt;FieldValueConduit&gt; provider = createFieldValueConduitProvider(field);<br /><br />    field.replaceAccess(provider);<br />}</pre> 
<p>The transform() method is the lone method for this class, as defined by <a href="http://tapestry.apache.org/tapestry5.2-dev/apidocs/org/apache/tapestry5/services/ComponentClassTransformWorker.html">ComponentClassTransformWorker</a>. It uses a method on the <a href="http://tapestry.apache.org/tapestry5.2-dev/apidocs/org/apache/tapestry5/services/ClassTransformation.html">ClassTransformation</a> to locate all the unclaimed fields. <a href="http://tapestry.apache.org/tapestry5.2-dev/apidocs/org/apache/tapestry5/services/TransformField.html">TransformField</a> is the representation of a field of a component class during the transformation process. As we'll see it is very easy to intercept access to the field.</p> 
<p>Some of those fields are final or static and are just ignored. A <a href="http://tapestry.apache.org/tapestry5.2-dev/apidocs/org/apache/tapestry5/services/ComponentValueProvider.html">ComponentValueProvider</a> is a callback object: when the component (whatever it is) is first instantiated, the provider will be invoked and the return value stored into a new field. A <a href="http://tapestry.apache.org/tapestry5.2-dev/apidocs/org/apache/tapestry5/ioc/services/FieldValueConduit.html">FieldValueConduit</a> is an object that takes over responsibility for access to a TransformField: internally, all read and write access to the field is passed through the conduit object.</p> 
<p>So, what we're saying is: when the component is first created, use the callback to create a conduit, and change any read or write access to the field to pass through the created conduit. If a component is instantiated multiple times (either in different pages, or within the same page) each instance of the component will end up with a specific FieldValueConduit.</p> 
<p>Fine so far; it comes down to what's inside the createFieldValueConduitProvider() method:</p> 
<pre>private ComponentValueProvider&lt;FieldValueConduit&gt; createFieldValueConduitProvider(TransformField field)<br />{<br />    final String fieldName = field.getName();<br />    final String fieldType = field.getType();<br /><br />    return new ComponentValueProvider&lt;FieldValueConduit&gt;()<br />    {<br />        public FieldValueConduit get(ComponentResources resources)<br />        {<br />            Object fieldDefaultValue = classCache.defaultValueForType(fieldType);<br /><br />            String key = String.format(&quot;UnclaimedFieldWorker:%s/%s&quot;, resources.getCompleteId(), fieldName);<br /><br />            return new UnclaimedFieldConduit((InternalComponentResources) resources,<br />                    perThreadManager.createValue(key), fieldDefaultValue);<br />        }<br />    };<br />}</pre> 
<p>Here we capture the name of the field and its type (expressed as String). Inside the get() method we determine the initial default value for the field: typically just null, but may be 0 (for a primitive numeric field) or false (for a primitive boolean field).</p> 
<p>Next we build a unique key used to store and retrieve the field's value inside the per-thread Map. The key includes the complete id of the component and the name of the field: thus two different component instances, in the same page or across different pages, will have their own unique key.</p> 
<p>We use the <a href="http://tapestry.apache.org/tapestry5.2-dev/apidocs/org/apache/tapestry5/ioc/services/PerthreadManager.html">PerthreadManager</a> service to create a <a href="http://tapestry.apache.org/tapestry5.2-dev/apidocs/org/apache/tapestry5/ioc/services/PerThreadValue.html">PerThreadValue</a> for the field.</p> 
<p>Lastly, we create the conduit object. Let's look at the conduit in more detail:</p> 
<pre>static class UnclaimedFieldConduit implements FieldValueConduit<br />{<br />    private final InternalComponentResources resources;<br /><br />    private final PerThreadValue&lt;Object&gt; fieldValue;<br /><br />    // Set prior to the containingPageDidLoad lifecycle event<br />    private Object fieldDefaultValue;<br /><br />    private UnclaimedFieldConduit(InternalComponentResources resources, PerThreadValue&lt;Object&gt; fieldValue,<br />            Object fieldDefaultValue)<br />    {<br />        this.resources = resources;<br /><br />        this.fieldValue = fieldValue;<br />        this.fieldDefaultValue = fieldDefaultValue;<br />    }</pre> 
<p>We use the special InternalComponentResources interface because we'll need to know if the page is loading, or in normal operation (that's coming up). We capture our initial guess at a default value for the field (remember: null, false or 0) but that may change.</p> 
<pre>public Object get()<br />{<br />    return fieldValue.exists() ? fieldValue.get() : fieldDefaultValue;<br />}</pre> 
<p>Whenever code inside the component reads the field, this method will be invoked. It checks to see if a value has been stored into the PerThreadValue object this request; if so the stored value is returned, otherwise the field default value is returned.</p> 
<p>Notice the distinction here between null and no value at all. Just because the field is set to null doesn't mean we should switch over the the default value (assuming the default is not null).</p> 
<p>The last hurdle is updates to the field:</p> 
<pre>public void set(Object newValue)<br />  {<br />      fieldValue.set(newValue);<br /><br />      // This catches the case where the instance initializer method sets a value for the field.<br />      // That value is captured and used when no specific value has been stored.<br /><br />      if (!resources.isLoaded())<br />          fieldDefaultValue = newValue;<br />  }</pre> 
<p>The basic logic is just to stuff the value assigned to the component field into the PerThreadValue object. However, there's one special case: a field initialization (whether it's in the component's constructor, or at the point in the code where the field is first defined) turns into a call to set(). We can differentiate the two cases because that update occurs before the page is marked as fully loaded, rather than in normal use of the page.</p> 
<p>And that's it! Now, to be honest, this is more detail than a typical Tapestry developer ever needs to know. However, it's a good demonstration of how Tapestry's class transformation APIs make Java code <em>fluid</em>; capable of being changed dynamically (under carefully controlled circumstances).</p> 
<p>Back to pooling: how is this going to affect performance? That's an open question, and putting together a performance testing environment is another task at the top of my list. My suspicion is that the new overhead will not make a visible difference for small applications (dozens of pages, reasonable number of concurrent users) ... but for high end sites (hundreds of pages, large numbers of concurrent users) the avoidance of pooling and page construction will make a <em>big</em> difference!</p> 
<p><em>From <a href="http://tapestryjava.blogspot.com/2010/07/everyone-out-of-pool-tapestry-goes.html">http://tapestryjava.blogspot.com/2010/07/everyone-out-of-pool-tapestry-goes.html</a></em></p> 
<p> </p> 
<p> </p> 
<p> </p> 
<p> </p> 
<p> </p> 
<p> </p> 
<p> </p> 
<p> </p> 
<p> </p> 
<p> </p></div></body></html>